package com.hhd.flow.engine.cmd.impl;

import com.alibaba.fastjson.JSONObject;
import com.hhd.flow.engine.cmd.PassTaskContext;
import com.hhd.flow.engine.utils.CommonUtils;
import com.hhd.flow.engine.utils.FlowModelWalker;
import com.hhd.flow.engine.utils.FlowUtils;
import com.hhd.flow.engine.vo.*;
import com.hhd.flow.entity.*;
import com.hhd.flow.service.IFlowGatewayExecutionService;
import com.hhd.flow.service.IFlowUserTaskHistoryService;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import org.apache.logging.log4j.util.Strings;

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

@Setter
@Getter
@SuperBuilder(toBuilder = true)
public class RetrieveUserTaskCommand extends BaseCommand<RetrieveUserTaskCommand.RetrieveUserTaskResult> {
    String formId;
    FlowUser user;
    Boolean removeComment;
    RetrieveUserTaskCommand.RetrieveUserTaskResult result = new RetrieveUserTaskResult();
    JSONObject formModel;
    boolean forAllUser;//退回以后其他用户任务是否重新创建

    //下面是运行的时候的数据
    FlowProcessInstance flowProcessInstance;
    FlowUserTaskHistory flowUserTaskHistory;
    FlowModel flowModel;
    UserTask retriveUserTask;
    HashSet<String> nextNodeIds = new LinkedHashSet<>();//包含了当前节点
    HashSet<String> nextLineIds = new LinkedHashSet<>();
    FlowPath flowPath;
    FlowTask activeFlowTask;
    FlowTask newFlowTask;
    FlowUserTask newFlowUserTask;


    @Override
    public RetrieveUserTaskResult execute() {
        createResult();

        loadData();

        if (result.hasError()) return result;

        checkRollbackAble();

        if (result.hasError()) return result;

        retrive();

        return result;
    }

    private void checkRollbackAble() {
        if (Boolean.FALSE.equals(flowProcessInstance.getCannotWidthdraw())) {
            result.error(flowProcessInstance.getCannotWithdrawMessage());
        }
    }

    private void retrive() {
        //发布任务取回之前的事件
        beforePublish();

        //如果当前这个节点还是活跃的
        if (isCurrentTaskActive()) {
            //删除一下用户任务的历史
            removeRetriveUserTaskHistory();

            //直接创建一个用户任务
            simpleCreateUserTaskForRetriveUserTask();

            //处理一下任务的执行方式
            simpleResolveUserTaskExecuteWay();
        } else {//否则

            if (!CommonUtils.isEmpty(nextNodeIds)) {
                //删除一下后面用户任务的审批历史
                removeNextUserTaskHistory();

                //删除一下后面节点的执行历史
                removeNextTaskHistory();

                //删除一下后面活跃的用户任务
                removeNextActiveUserTask();

                //删除一下后面节点的活跃的执行节点
                removeNextTask();
            }

            //处理一下网关的执行历史, 容易执行不下去, 或者网关失去作用..........
            //  在要回退的节点和所有的活跃节点之间, 如果遇到了网关, 那就删除网关的执行历史
            handleGateWayExecution();

            //创建一下当前节点的任务
            //创建一下当前节点的用户任务   注意: 如果只需要当前用户重新审批, 那么当前节点的执行方式还需要变化, 否则不变, 此时需要创建所有用户任务
            createNewFlowTask();

            //处理一下路径, 可能有问题, 但是无伤大雅, 不影响流程的执行
            //  把后面的路径删除一下
            handleFlowPath();
        }
        //删除一下当前这个节点的审批意见, 如果需要的话
        removeCommentIfNecessary();

        //创建一下任务取回的审批意见
        createComment();

        //重新生成一下所有用户的已办历史
        // 只需要删除当前用户的已办就好了
        removeFlowUserDone();

        //发布任务取回之后的事件
        afterPublish();
    }

    private void beforePublish() {
    }

    private void afterPublish() {
    }

    private void removeFlowUserDone() {
        getEngine().getFlowUserDoneService()
                .lambdaUpdate()
                .eq(FlowUserDone::getUserId, user.getId())
                .eq(FlowUserDone::getProcessInstanceId, flowProcessInstance.getId())
                .remove();
    }

    private void createComment() {
        if(Boolean.TRUE.equals(this.removeComment)) return; //在删除历史评论的情况下, 也没必要创建新的评论了
        FlowComment comment = FlowUtils.createComment(newFlowUserTask, "取回", null, null, CommentType.retrive, null);
        getEngine().getFlowCommentService().save(comment);
    }

    private void removeCommentIfNecessary() {
        if (Boolean.TRUE.equals(removeComment)) {
            getEngine().getFlowCommentService()
                    .lambdaUpdate()
                    .eq(FlowComment::getProcessInstanceId, flowProcessInstance.getId())
                    .eq(FlowComment::getTaskDefinitionId, retriveUserTask.getId())
                    .eq(FlowComment::getUserId, user.getId())
                    .remove();
        }
    }

    private void handleFlowPath() {
        Set<String> pathSet = getPathSet();

        FlowModelWalker walker = FlowUtils.walkerBegin(flowModel, retriveUserTask.getId());
        walker.walk(context -> {
            FlowNodeAndEdge nodeAndEdge = context.getCurrentNodeAndPath();
            pathSet.remove(nodeAndEdge.getNode().getId());
            pathSet.remove(nodeAndEdge.getEdge().getId());
        });
        pathSet.remove(retriveUserTask.getId());

        setPath(pathSet);

        getEngine().getFlowPathService().removeById(flowPath.getId());
        getEngine().getFlowPathService().save(flowPath);
    }

    private void setPath(Set<String> pathSet) {
        if (CommonUtils.isEmpty(pathSet)) {
            flowPath.setPath(null);
        } else {
            flowPath.setPath(Strings.join(pathSet, ','));
        }
    }

    private Set<String> getPathSet() {
        LinkedHashSet<String> set = new LinkedHashSet<>();
        String path = flowPath.getPath();
        if (CommonUtils.isEmpty(path)) return set;
        for (String s : path.split(",")) {
            set.add(s);
        }
        return set;
    }

    private void createNewFlowTask() {
        PassTaskContext context = createTemplatePassTaskContext();
        List<FlowUser> flowUsers = loadFlowUsers(context);

        FlowTask flowTask = FlowUtils.createFlowTask(retriveUserTask, flowUsers, context);
        getEngine().getFlowTaskService().save(flowTask);

        List<FlowUserTask> flowUserTasks = FlowUtils.createFlowUserTasks(flowTask, flowUsers, flowProcessInstance, retriveUserTask);
        getEngine().getFlowUserTaskService().saveBatch(flowUserTasks);

        for (FlowUserTask flowUserTask : flowUserTasks) {
            if (flowUserTask.getUserId().equals(user.getId())) {
                newFlowUserTask = flowUserTask;
            }
        }
    }

    private List<FlowUser> loadFlowUsers(PassTaskContext context) {
        if (this.forAllUser) {
            return getEngine().loadFlowUser(flowProcessInstance.getFormTypeId(), flowProcessInstance.getFormId(), retriveUserTask, context);
        }
        List<FlowUser> res = CommonUtils.newList();
        res.add(user);
        return res;
    }

    private PassTaskContext createTemplatePassTaskContext() {
        return PassTaskContext.builder()
                .flowProcessInstance(flowProcessInstance)
                .formTypeId(flowProcessInstance.getFormTypeId())
                .formId(flowProcessInstance.getFormId())
                .formModel(formModel)
                .build();
    }


    private void handleGateWayExecution() {
        FlowModelWalker walker = FlowUtils.walkerBegin(flowModel, retriveUserTask.getId());
        HashSet<String> set = new HashSet<>();
        walker.walk(context -> {
            FlowNodeAndEdge currentNodeAndPath = context.getCurrentNodeAndPath();
            FlowNode node = currentNodeAndPath.getNode();
            if (FlowNode.FlowNodeType.gateway == node.getType()) {
                set.add(node.getId());
            }
        });

        if (CommonUtils.isEmpty(set)) return;
        IFlowGatewayExecutionService executionService = getEngine().getFlowGatewayExecutionService();
        executionService.lambdaUpdate()
                .in(FlowGatewayExecution::getStartGatewayDefinitionId, set)
                .remove();

        executionService.lambdaUpdate()
                .in(FlowGatewayExecution::getEndGatewayDefinitionId, set)
                .remove();
    }

    private void removeNextTask() {
        getEngine().getFlowTaskService()
                .lambdaUpdate()
                .in(FlowTask::getTaskDefinitionId, nextNodeIds)
                .eq(FlowTask::getProcessInstanceId, flowProcessInstance.getId())
                .remove();
    }

    private void removeNextActiveUserTask() {
        getEngine().getFlowUserTaskService()
                .lambdaUpdate()
                .in(FlowUserTask::getTaskDefinitionId, nextNodeIds)
                .eq(FlowUserTask::getProcessInstanceId, flowProcessInstance.getId())
                .remove();
    }

    private void removeNextTaskHistory() {
        getEngine().getFlowTaskHistoryService()
                .lambdaUpdate()
                .in(FlowTaskHistory::getTaskDefinitionId, nextNodeIds)
                .eq(FlowTaskHistory::getProcessInstanceId, flowProcessInstance.getId())
                .remove();
    }

    private void removeNextUserTaskHistory() {
        getEngine().getFlowUserTaskHistoryService()
                .lambdaUpdate()
                .in(FlowUserTaskHistory::getTaskDefinitionId, nextNodeIds)
                .eq(FlowUserTaskHistory::getProcessInstanceId, flowProcessInstance.getId())
                .remove();
    }

    private void simpleResolveUserTaskExecuteWay() {
        activeFlowTask.setExecuteCount(activeFlowTask.getExecuteCount() - 1);
        getEngine().getFlowTaskService()
                .save(activeFlowTask);
    }

    private void simpleCreateUserTaskForRetriveUserTask() {
        FlowTask flowTask = activeFlowTask;
        newFlowUserTask = FlowUtils.createFlowUserTask(flowTask, user, flowProcessInstance, retriveUserTask);
        getEngine().getFlowUserTaskService().save(newFlowUserTask);
        
    }

    private void removeRetriveUserTaskHistory() {
        getEngine().getFlowUserTaskHistoryService()
                .lambdaUpdate()
                .eq(FlowUserTaskHistory::getProcessInstanceId, flowProcessInstance.getId())
                .eq(FlowUserTaskHistory::getUserId, user.getId())
                .eq(FlowUserTaskHistory::getTaskDefinitionId, retriveUserTask.getId())
                .remove();
    }

    private boolean isCurrentTaskActive() {
        List<FlowTask> list = getEngine().getFlowTaskService()
                .lambdaQuery()
                .eq(FlowTask::getTaskDefinitionId, retriveUserTask.getId())
                .eq(FlowTask::getProcessInstanceId, flowProcessInstance.getId())
                .list();
        if (CommonUtils.isEmpty(list)) { //空的 表示已经不活跃了
            return false;
        }
        activeFlowTask = list.get(0);
        return true;//不是空的, 表示还活跃
    }

    private void createResult() {
        result = getRetrieveUserTaskResult();
        nextNodeIds = new LinkedHashSet<>();
        nextLineIds = new LinkedHashSet<>();
    }

    private static RetrieveUserTaskResult getRetrieveUserTaskResult() {
        return new RetrieveUserTaskResult();
    }

    private void loadData() {
        tryLoadFlowProcessInstance(); //尝试加载正在执行的流程实例

        if (result.hasError()) return;

        tryLoadFlowUserTaskHistory(); //尝试加载正在执行的流程实例

        if (result.hasError()) return;

        tryLoadFlowModel(); //加载流程图

        if (result.hasError()) return;

        checkFlowProcessInstance(); //检查一下当前的流程状态, 如果下一个节点已经有用户审批过了, 那还是不让回退

        if (result.hasError()) return;

        tryLoadFlowPath(); //加载流程的运转路径

        loadCurrentFlowUser();//获取要取回的用户任务的节点
    }

    private void loadCurrentFlowUser() {
        retriveUserTask = flowModel.getUserTaskById(flowUserTaskHistory.getTaskDefinitionId());
        if (CommonUtils.isEmpty(retriveUserTask)) {
            result.error("没有找到用户任务的定义: " + flowUserTaskHistory.getTaskName());
        }
    }

    private void tryLoadFlowPath() {
        List<FlowPath> list = getEngine().getFlowPathService().lambdaQuery()
                .eq(FlowPath::getProcessInstanceId, flowProcessInstance.getId())
                .list();
        if (CommonUtils.isEmpty(list)) {
            result.error("没有找到流程的路径: " + flowProcessInstance.getProcessTitle());
            return;
        }
        flowPath = list.get(0);
    }

    private void tryLoadFlowModel() {
        String model = flowProcessInstance.getModel();
        if (CommonUtils.isEmpty(model)) {
            String processDefinitionId = flowProcessInstance.getProcessDefinitionId();
            if (CommonUtils.isEmpty(processDefinitionId)) {
                result.error("没有找到流程定义的id: " + flowProcessInstance.getProcessTitle());
                return;
            }
            FlowProcessDefinitionRef flowProcessDefinitionRef = getEngine().getFlowProcessDefinitionRefService().getById(processDefinitionId);
            if (CommonUtils.isEmpty(flowProcessDefinitionRef)) {
                result.error("没有找到流程的定义: " + processDefinitionId);
                return;
            }
            model = flowProcessDefinitionRef.getModel();
        }
        doParseModel(model);
    }

    private void doParseModel(String model) {
        try {
            flowModel = FlowModel.forString(model);
        } catch (Exception e) {
            result.error("解析流程图失败: " + e.getMessage());
        }
    }

    private void tryLoadFlowUserTaskHistory() {
        IFlowUserTaskHistoryService flowUserTaskHistoryService = getEngine().getFlowUserTaskHistoryService();
        List<FlowUserTaskHistory> list = flowUserTaskHistoryService.lambdaQuery()
                .eq(FlowUserTaskHistory::getUserId, user.getId())
                .eq(FlowUserTaskHistory::getProcessInstanceId, flowProcessInstance.getId())
                .orderByDesc(FlowUserTaskHistory::getCompleteTime)
                .list();
        if (CommonUtils.isEmpty(list)) {
            result.error("未找到当前人员的审批历史");
            return;
        }
        flowUserTaskHistory = list.get(0);
    }

    private void checkFlowProcessInstance() {
        FlowNode flowNode = flowModel.getNodeById(flowUserTaskHistory.getTaskDefinitionId());
        if (CommonUtils.isEmpty(flowNode)) {
            result.error("没有找到任务定义: " + flowUserTaskHistory.getTaskName());
            return;
        }

        getNextNodeIds(flowNode);

        HashSet<String> set = new HashSet<>(nextNodeIds);
        set.remove(flowNode.getId());
        List<FlowUserTaskHistory> list = getEngine().getFlowUserTaskHistoryService()
                .lambdaQuery()
                .in(FlowUserTaskHistory::getTaskDefinitionId, set)
                .eq(FlowUserTaskHistory::getProcessInstanceId, flowProcessInstance.getId())
                .list();
        if (!CommonUtils.isEmpty(list)) {
            //检查一下下一个节点有没有被用户审批过
            // 如果被审批过, 那就报错了
            result.error("流程已经被审批过了, 无法取回");
            return;
        }
    }

    private void getNextNodeIds(FlowNode flowNode) {
        List<FlowNode> list = CommonUtils.newList();
        list.add(flowNode);
        nextNodeIds.add(flowNode.getId());

        while (true) {
            List<FlowNode> tempList = CommonUtils.newList();
            for (FlowNode node : list) {
                List<FlowNodeAndEdge> nextNodeAndEdges = flowModel.getNextNodeAndEdgesById(node.getId());
                if (CommonUtils.isEmpty(nextNodeAndEdges)) continue;

                for (FlowNodeAndEdge nextNodeAndEdge : nextNodeAndEdges) {
                    nextLineIds.add(nextNodeAndEdge.getEdge().getId());
                    if (nextNodeIds.contains(nextNodeAndEdge.getNode().getId())) continue;
                    nextNodeIds.add(nextNodeAndEdge.getNode().getId());

                    tempList.add(nextNodeAndEdge.getNode());
                }
            }
            if (CommonUtils.isEmpty(tempList)) break;
            list = tempList;
        }
    }

    private void tryLoadFlowProcessInstance() {
        List<FlowProcessInstance> instances = getEngine().getFlowProcessInstanceService().lambdaQuery().eq(FlowProcessInstance::getFormId, getFormId()).list();
        if (CommonUtils.isEmpty(instances)) {
            result.error("流程已结束, 无法执行取回操作");
            return;
        }
        flowProcessInstance = instances.get(0);
    }

    @Getter
    @Setter
    public static class RetrieveUserTaskResult {
        boolean success = true;
        String message = "取回完成";

        public boolean hasError() {
            return !success;
        }

        public void error(String message) {
            this.message = message;
            this.success = false;
        }
    }
}
